home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / pascal / tpcom.zip / IBMCOM.PAS next >
Pascal/Delphi Source File  |  1992-08-11  |  18KB  |  689 lines

  1. UNIT ibmcom;
  2.  
  3. {Version 3.1    8/11/92 rob lerner}
  4.  
  5. {This unit is the communications port interrupt driver for the IBM-PC.
  6. It handles handles all low-level i/o through the serial port.  It is
  7. installed by calling Cominstall.  It deinstalls itself automatically
  8. when the program exits, or you can deinstall it by calling Comdeinstall.
  9.  
  10. Donated to the public domain by Wayne E. Conrad, January, 1989.
  11. If you have any problems or suggestions, please contact me at my BBS:
  12.  
  13.     Pascalaholics Anonymous
  14.     (602) 484-9356
  15.     2400 bps
  16.     The home of WBBS
  17.     Lots of source code
  18.  
  19. Modified on 8/11/92 for procedure names to be better aligned to function
  20. rob lerner
  21.  
  22. }
  23.  
  24.  
  25. INTERFACE
  26.  
  27. USES
  28.   Dos;
  29.  
  30.  
  31. TYPE
  32.   Comparity = (ComNone, ComEven, ComOdd, ComZero, ComOne);
  33.  
  34.  
  35. PROCEDURE ComFlushRx;
  36. PROCEDURE ComFlushTx;
  37. FUNCTION  Comcarrier: Boolean;
  38. FUNCTION  ComRx: Char;
  39. FUNCTION  ComTxReady: Boolean;
  40. FUNCTION  ComTxEmpty: Boolean;
  41. FUNCTION  ComRxEmpty: Boolean;
  42. PROCEDURE ComTx (ch: Char);
  43. PROCEDURE ComRxString (st: String);
  44. PROCEDURE ComLowerDTR;
  45. PROCEDURE ComRaiseDTR;
  46. PROCEDURE ComSetSpeed (speed: Word);
  47. PROCEDURE ComSetParity (parity: Comparity; stop_bits: Byte);
  48. PROCEDURE ComInstall
  49.   (
  50.   portnum  : Word;
  51.   VAR error: Word
  52.   );
  53. PROCEDURE Comdeinstall;
  54.  
  55.  
  56. IMPLEMENTATION
  57.  
  58.  
  59. {Summary of IBM-PC Asynchronous Adapter Registers.  From:
  60.   Compute!'s Mapping the IBM PC and PCjr, by Russ Davis
  61.   (Greensboro, North Carolina, 1985: COMPUTE! Publications, Inc.),
  62.   pp. 290-292.
  63.  
  64. Addresses given are for COM1 and COM2, respectively.  The names given
  65. in parentheses are the names used in this module.
  66.  
  67.  
  68. 3F8/2F8 (uart_data) Read: transmit buffer.  Write: receive buffer, or baud
  69. rate divisor LSB if port 3FB, bit 7 = 1.
  70.  
  71. 3F9/2F9 (uart_ier) Write: Interrupt enable register or baud rate divisor
  72. MSB if port 3FB, bit 7 = 1.
  73. PCjr baud rate divisor is different from other models;
  74. clock input is 1.7895 megahertz rather than 1.8432 megahertz.
  75. Interrupt enable register:
  76.     bits 7-4  forced to 0
  77.     bit 3     1=enable change-in-modem-status interrupt
  78.     bit 2     1=enable line-status interrupt
  79.     bit 1     1=enable transmit-register-empty interrupt
  80.     bit 0     1=data-available interrupt
  81.  
  82. 3FA/2FA (uart_iir) Interrupt identification register (prioritized)
  83.      bits 7-3  forced to 0
  84.      bits 2-1  00=change-in-modem-status (lowest)
  85.      bits 2-1  01=transmit-register-empty (low)
  86.      bits 2-1  10=data-available (high)
  87.      bits 2-1  11=line status (highest)
  88.      bit 0     1=no interrupt pending
  89.      bit 0     0=interrupt pending
  90.  
  91. 3FB/2FB (uart_lcr) Line control register
  92.      bit 7  0=normal, 1=address baud rate divisor registers
  93.      bit 6  0=break disabled, 1=enabled
  94.      bit 5  0=don't force parity
  95.             1=if bit 4-3=01 parity always 1
  96.               if bit 4-3=11 parity always 0
  97.               if bit 3=0 no parity
  98.      bit 4  0=odd parity,1=even
  99.      bit 3  0=no parity,1=parity
  100.      bit 2  0=1 stop bit
  101.             1=1.5 stop bits if 5 bits/character or
  102.               2 stop bits if 6-8 bits/character
  103.      bits 1-0  00=5 bits/character
  104.                01=6 bits/character
  105.                10=7 bits/character
  106.                11=8 bits/character
  107.  
  108.      bits 5..3: 000 No parity
  109.                 001 Odd parity
  110.                 010 No parity
  111.                 011 Even parity
  112.                 100 No parity
  113.                 101 Parity always 1
  114.                 110 No parity
  115.                 111 Parity always 0
  116.  
  117.  
  118. 3FC/2FC (uart_mcr) Modem control register
  119.      bits 7-5  forced to zero
  120.      bit 4     0=normal, 1=loop back test
  121.      bits 3-2  all PCs except PCjr
  122.      bit 3     1=interrupts to system bus, user-designated output: OUT2
  123.      bit 2     user-designated output, OUT1
  124.      bit 1     1=activate rts
  125.      bit 0     1=activate dtr
  126.  
  127. 3FD/2FD (uart_lsr) Line status register
  128.      bit 7  forced to 0
  129.      bit 6  1=transmit shift register is empty
  130.      bit 5  1=transmit hold register is empty
  131.      bit 4  1=break received
  132.      bit 3  1=framing error received
  133.      bit 2  1=parity error received
  134.      bit 1  1=overrun error received
  135.      bit 0  1=data received
  136.  
  137. 3FE/2FE (uart_msr) Modem status register
  138.      bit 7  1=receive line signal detect
  139.      bit 6  1=ring indicator (all PCs except PCjr)
  140.      bit 5  1=dsr
  141.      bit 4  1=cts
  142.      bit 3  1=receive line signal detect has changed state
  143.      bit 2  1=ring indicator has changed state (all PCs except PCjr)
  144.      bit 1  1=dsr has changed state
  145.      bit 0  1=cts has changed state
  146.  
  147. 3FF/2FF (uart_spr) Scratch pad register.}
  148.  
  149.  
  150. {Maximum port number (minimum is 1) }
  151.  
  152. CONST
  153.   max_port = 4;
  154.  
  155.  
  156. {Base i/o address for each COM port}
  157.  
  158. CONST
  159.   uart_base: ARRAY [1..max_port] OF Integer = ($3F8, $2F8, $3E8, $2E8);
  160.  
  161.  
  162. {Interrupt numbers for each COM port}
  163.  
  164. CONST
  165.   intnums: ARRAY [1..max_port] OF Byte = ($0C, $0B, $0C, $0B);
  166.  
  167.  
  168. {i8259 interrupt levels for each port}
  169.  
  170. CONST
  171.   i8259levels: ARRAY [1..max_port] OF Byte = (4, 3, 4, 3);
  172.  
  173.  
  174. {This variable is TRUE if the interrupt driver has been installed, or FALSE
  175. if it hasn't.  It's used to prevent installing twice or deinstalling when not
  176. installed.}
  177.  
  178. CONST
  179.   ComInstalled: Boolean = False;
  180.  
  181.  
  182. {UART i/o addresses.  Values depend upon which COMM port is selected.}
  183.  
  184. VAR
  185.   uart_data: Word;             {Data register}
  186.   uart_ier : Word;             {Interrupt enable register}
  187.   uart_iir : Word;             {Interrupt identification register}
  188.   uart_lcr : Word;             {Line control register}
  189.   uart_mcr : Word;             {Modem control register}
  190.   uart_lsr : Word;             {Line status register}
  191.   uart_msr : Word;             {Modem status register}
  192.   uart_spr : Word;             {Scratch pad register}
  193.  
  194.  
  195. {Original contents of IER and MCR registers.  Used to restore UART
  196. to whatever state it was in before this driver was loaded.}
  197.  
  198. VAR
  199.   old_ier: Byte;
  200.   old_mcr: Byte;
  201.  
  202.  
  203. {Original contents of interrupt vector.  Used to restore the vector when
  204. the interrupt driver is deinstalled.}
  205.  
  206. VAR
  207.   old_vector: Pointer;
  208.  
  209.  
  210. {Original contents of interrupt controller mask.  Used to restore the
  211. bit pertaining to the comm controller we're using.}
  212.  
  213. VAR
  214.   old_i8259_mask: Byte;
  215.  
  216.  
  217. {Bit mask for i8259 interrupt controller}
  218.  
  219. VAR
  220.   i8259bit: Byte;
  221.  
  222.  
  223. {Interrupt vector number}
  224.  
  225. VAR
  226.   intnum: Byte;
  227.  
  228.  
  229. {Receive queue.  Received characters are held here until retrieved by
  230. ComRx.}
  231.  
  232. CONST
  233.   rx_queue_size = 128;   {Change to suit}
  234. VAR
  235.   rx_queue: ARRAY [1..rx_queue_size] OF Byte;
  236.   rx_in   : Word;        {Index of where to store next character}
  237.   rx_out  : Word;        {Index of where to retrieve next character}
  238.   rx_chars: Word;        {Number of chars in queue}
  239.  
  240.  
  241. {Transmit queue.  Characters to be transmitted are held here until the
  242. UART is ready to transmit them.}
  243.  
  244. CONST
  245.   tx_queue_size = 16;    {Change to suit}
  246. VAR
  247.   tx_queue: ARRAY [1..tx_queue_size] OF Byte;
  248.   tx_in   : Integer;     {Index of where to store next character}
  249.   tx_out  : Integer;     {Index of where to retrieve next character}
  250.   tx_chars: integer;     {Number of chars in queue}
  251.  
  252.  
  253. {This variable is used to save the next link in the "exit procedure" chain.}
  254.  
  255. VAR
  256.   exit_save: Pointer;
  257.  
  258.  
  259. {$I ints.inc}   {Macros for enabling and disabling interrupts}
  260.  
  261.  
  262. {Interrupt driver.  The UART is programmed to cause an interrupt whenever
  263. a character has been received or when the UART is ready to transmit another
  264. character.}
  265.  
  266. {$R-,S-}
  267. PROCEDURE Cominterrupt_driver; INTERRUPT;
  268.  
  269. VAR
  270.   ch   : Char;
  271.   iir  : Byte;
  272.   dummy: Byte;
  273.  
  274. BEGIN
  275.  
  276.   {While bit 0 of the interrupt identification register is 0, there is an
  277.   interrupt to process}
  278.  
  279.   iir := Port [uart_iir];
  280.  
  281.   WHILE NOT Odd (iir) DO
  282.     BEGIN
  283.  
  284.     CASE iir SHR 1 OF
  285.  
  286.       {iir = 100b: Received data available.  Get the character, and if
  287.       the buffer isn't full, then save it.  If the buffer is full,
  288.       then ignore it.}
  289.  
  290.       2:
  291.         BEGIN
  292.         ch := Char (Port [uart_data] );
  293.         IF (rx_chars <= rx_queue_size) THEN
  294.           BEGIN
  295.           rx_queue [rx_in] := Ord (ch);
  296.           Inc (rx_in);
  297.           IF rx_in > rx_queue_size THEN
  298.             rx_in := 1;
  299.           rx_chars := Succ (rx_chars);
  300.           END;
  301.         END;
  302.  
  303.       {iir = 010b: Transmit register empty.  If the transmit buffer
  304.       is empty, then disable the transmitter to prevent any more
  305.       transmit interrupts.  Otherwise, send the character.
  306.  
  307.       The test of the line-status-register is to see if the transmit
  308.       holding register is truly empty.  Some UARTS seem to cause transmit
  309.       interrupts when the holding register isn't empty, causing transmitted
  310.       characters to be lost.}
  311.  
  312.       1:
  313.         IF (tx_chars <= 0) THEN
  314.           Port [uart_ier] := Port [uart_ier] AND NOT 2
  315.         ELSE
  316.           IF Odd (Port [uart_lsr] SHR 5) THEN
  317.             BEGIN
  318.             Port [uart_data] := tx_queue [tx_out];
  319.             Inc (tx_out);
  320.             IF tx_out > tx_queue_size THEN
  321.               tx_out := 1;
  322.             Dec (tx_chars);
  323.             END;
  324.  
  325.       {iir = 001b: Change in modem status.  We don't expect this interrupt,
  326.       but if one ever occurs we need to read the line status to reset it
  327.       and prevent an endless loop.}
  328.  
  329.       0:
  330.         dummy := Port [uart_msr];
  331.  
  332.       {iir = 111b: Change in line status.  We don't expect this interrupt,
  333.       but if one ever occurs we need to read the line status to reset it
  334.       and prevent an endless loop.}
  335.  
  336.       3:
  337.         dummy := Port [uart_lsr];
  338.  
  339.       END;
  340.  
  341.     iir := Port [uart_iir];
  342.     END;
  343.  
  344.   {Tell the interrupt controller that we're done with this interrupt}
  345.  
  346.   Port [$20] := $20;
  347.  
  348. END;
  349. {$R+,S+}
  350.  
  351.  
  352. {Flush (empty) the receive buffer.}
  353.  
  354. PROCEDURE ComFlushRx;
  355. BEGIN
  356.   disable_interrupts;
  357.   rx_chars := 0;
  358.   rx_in    := 1;
  359.   rx_out   := 1;
  360.   enable_interrupts;
  361. END;
  362.  
  363.  
  364. {Flush (empty) transmit buffer.}
  365.  
  366. PROCEDURE ComFlushTx;
  367. BEGIN
  368.   disable_interrupts;
  369.   tx_chars := 0;
  370.   tx_in    := 1;
  371.   tx_out   := 1;
  372.   enable_interrupts;
  373. END;
  374.  
  375.  
  376. {This function returns TRUE if a carrier is present.}
  377.  
  378. FUNCTION Comcarrier: Boolean;
  379. BEGIN
  380.   Comcarrier := ComInstalled AND Odd (Port [uart_msr] SHR 7);
  381. END;
  382.  
  383.  
  384. {Get a character from the receive buffer.  If the buffer is empty, return
  385. a NULL (#0).}
  386.  
  387. FUNCTION ComRx: Char;
  388. BEGIN
  389.   IF NOT ComInstalled OR (rx_chars = 0) THEN
  390.     ComRx := #0
  391.   ELSE
  392.     BEGIN
  393.     disable_interrupts;
  394.     ComRx := Chr (rx_queue [rx_out] );
  395.     Inc (rx_out);
  396.     IF rx_out > rx_queue_size THEN
  397.       rx_out := 1;
  398.     Dec (rx_chars);
  399.     enable_interrupts;
  400.     END;
  401. END;
  402.  
  403.  
  404. {This function returns True if ComTx can accept a character.}
  405.  
  406. FUNCTION ComTxReady: Boolean;
  407. BEGIN
  408.   ComTxReady := (tx_chars < tx_queue_size) OR NOT ComInstalled;
  409. END;
  410.  
  411.  
  412. {This function returns True if the transmit buffer is empty.}
  413.  
  414. FUNCTION ComTxEmpty: Boolean;
  415. BEGIN
  416.   ComTxEmpty := (tx_chars = 0) OR NOT ComInstalled;
  417. END;
  418.  
  419.  
  420. {This function returns True if the receive buffer is empty.}
  421.  
  422. FUNCTION ComRxEmpty: Boolean;
  423. BEGIN
  424.   ComRxEmpty := (rx_chars = 0) OR NOT ComInstalled;
  425. END;
  426.  
  427.  
  428. {Send a character.  Waits until the transmit buffer isn't full, then puts
  429. the character into it.  The interrupt driver will send the character
  430. once the character is at the head of the transmit queue and a transmit
  431. interrupt occurs.}
  432.  
  433. PROCEDURE ComTx (ch: Char);
  434. BEGIN
  435.   IF ComInstalled THEN
  436.     BEGIN
  437.     REPEAT UNTIL ComTxReady;
  438.     disable_interrupts;
  439.     tx_queue [tx_in] := Ord (ch);
  440.     IF tx_in < tx_queue_size THEN
  441.       Inc (tx_in)
  442.     ELSE
  443.       tx_in := 1;
  444.     Inc (tx_chars);
  445.     Port [uart_ier] := Port [uart_ier] OR 2;
  446.     enable_interrupts;
  447.     END;
  448. END;
  449.  
  450.  
  451. {Send a whole string}
  452.  
  453. PROCEDURE ComRxString (st: String);
  454. VAR
  455.   i: Byte;
  456. BEGIN
  457.   FOR i := 1 TO Length (st) DO
  458.     ComTx (st [i] );
  459. END;
  460.  
  461.  
  462. {Lower (deactivate) the DTR line.  Causes most modems to hang up.}
  463.  
  464. PROCEDURE ComLowerDTR;
  465. BEGIN
  466.   IF ComInstalled THEN
  467.     BEGIN
  468.     disable_interrupts;
  469.     Port [uart_mcr] := Port [uart_mcr] AND NOT 1;
  470.     enable_interrupts;
  471.     END;
  472. END;
  473.  
  474.  
  475. {Raise (activate) the DTR line.}
  476.  
  477. PROCEDURE ComRaiseDTR;
  478. BEGIN
  479.   IF ComInstalled THEN
  480.     BEGIN
  481.     disable_interrupts;
  482.     Port [uart_mcr] := Port [uart_mcr] OR 1;
  483.     enable_interrupts;
  484.     END;
  485. END;
  486.  
  487.  
  488. {Set the baud rate.  Accepts any speed between 2 and 65535.  However,
  489. I am not sure that extremely high speeds (those above 19200) will
  490. always work, since the baud rate divisor will be six or less, where a
  491. difference of one can represent a difference in baud rate of
  492. 3840 bits per second or more.}
  493.  
  494. PROCEDURE ComSetSpeed (speed: Word);
  495. VAR
  496.   divisor: Word;
  497. BEGIN
  498.   IF ComInstalled THEN
  499.     BEGIN
  500.     IF speed < 2 THEN speed := 2;
  501.     divisor := 115200 DIV speed;
  502.     disable_interrupts;
  503.     Port  [uart_lcr]  := Port [uart_lcr] OR $80;
  504.     Portw [uart_data] := divisor;
  505.     Port  [uart_lcr]  := Port [uart_lcr] AND NOT $80;
  506.     enable_interrupts;
  507.     END;
  508. END;
  509.  
  510.  
  511. {Set the parity and stop bits as follows:
  512.  
  513.   Comnone    8 data bits, no parity
  514.   Comeven    7 data bits, even parity
  515.   Comodd     7 data bits, odd parity
  516.   Comzero    7 data bits, parity always zero
  517.   Comone     7 data bits, parity always one}
  518.  
  519. PROCEDURE ComSetParity (parity: Comparity; stop_bits: Byte);
  520. VAR
  521.   lcr: Byte;
  522. BEGIN
  523.   CASE parity OF
  524.     Comnone: lcr := $00 OR $03;
  525.     Comeven: lcr := $18 OR $02;
  526.     Comodd : lcr := $08 OR $02;
  527.     Comzero: lcr := $38 OR $02;
  528.     Comone : lcr := $28 OR $02;
  529.     END;
  530.   IF stop_bits = 2 THEN
  531.     lcr := lcr OR $04;
  532.   disable_interrupts;
  533.   Port [uart_lcr] := Port [uart_lcr] AND $40 OR lcr;
  534.   enable_interrupts;
  535. END;
  536.  
  537. {Install the communications driver.  Portnum should be 1..max_port.
  538. Error codes returned are:
  539.  
  540.   0 - No error
  541.   1 - Invalid port number
  542.   2 - UART for that port is not present
  543.   3 - Already installed, new installation ignored}
  544.  
  545. PROCEDURE ComInstall
  546.   (
  547.   portnum  : Word;
  548.   VAR error: Word
  549.   );
  550. VAR
  551.   ier: Byte;
  552. BEGIN
  553.   IF ComInstalled THEN
  554.     error := 3
  555.   ELSE
  556.     IF (portnum < 1) OR (portnum > max_port) THEN
  557.       error := 1
  558.     ELSE
  559.       BEGIN
  560.  
  561.       {Set i/o addresses and other hardware specifics for selected port}
  562.  
  563.       uart_data := uart_base [portnum];
  564.       uart_ier  := uart_data + 1;
  565.       uart_iir  := uart_data + 2;
  566.       uart_lcr  := uart_data + 3;
  567.       uart_mcr  := uart_data + 4;
  568.       uart_lsr  := uart_data + 5;
  569.       uart_msr  := uart_data + 6;
  570.       uart_spr  := uart_data + 7;
  571.       intnum    := intnums [portnum];
  572.       i8259bit  := 1 SHL i8259levels [portnum];
  573.  
  574.       {Return error if hardware not installed}
  575.  
  576.       old_ier := Port [uart_ier];
  577.       Port [uart_ier] := 0;
  578.       IF Port [uart_ier] <> 0 THEN
  579.         error := 2
  580.       ELSE
  581.         BEGIN
  582.         error := 0;
  583.  
  584.         {Save original interrupt controller mask, then disable the
  585.         interrupt controller for this interrupt.}
  586.  
  587.         disable_interrupts;
  588.         old_i8259_mask := Port [$21];
  589.         Port [$21] := old_i8259_mask OR i8259bit;
  590.         enable_interrupts;
  591.  
  592.         {Clear the transmit and receive queues}
  593.  
  594.         ComFlushTx;
  595.         ComFlushRx;
  596.  
  597.         {Save current interrupt vector, then set the interrupt vector to
  598.         the address of our interrupt driver.}
  599.  
  600.         GetIntVec (intnum, old_vector);
  601.         SetIntVec (intnum, @Cominterrupt_driver);
  602.         ComInstalled := True;
  603.  
  604.         {Set parity to none, turn off BREAK signal, and make sure
  605.         we're not addressing the baud rate registers.}
  606.  
  607.         Port [uart_lcr] := 3;
  608.  
  609.         {Save original contents of modem control register, then enable
  610.         interrupts to system bus and activate RTS.  Leave DTR the way
  611.         it was.}
  612.  
  613.         disable_interrupts;
  614.         old_mcr := Port [uart_mcr];
  615.         Port [uart_mcr] := $A OR (old_mcr AND 1);
  616.         enable_interrupts;
  617.  
  618.         {Enable interrupt on data-available.  The interrupt for
  619.         transmit-ready is enabled when a character is put into the
  620.         transmit queue, and disabled when the transmit queue is empty.}
  621.  
  622.         Port [uart_ier] := 1;
  623.  
  624.         {Enable the interrupt controller for this interrupt.}
  625.  
  626.         disable_interrupts;
  627.         Port [$21] := Port [$21] AND NOT i8259bit;
  628.         enable_interrupts;
  629.  
  630.         END;
  631.       END;
  632. END;
  633.  
  634.  
  635. {Deinstall the interrupt driver completely.  It doesn't change the baud rate
  636. or mess with DTR; it tries to leave the interrupt vectors and enables and
  637. everything else as it was when the driver was installed.
  638.  
  639. This procedure MUST be called by the exit procedure of this module before
  640. the program exits to DOS, or the interrupt driver will still
  641. be attached to its vector -- the next communications interrupt that came
  642. along would jump to the interrupt driver which is no longer protected and
  643. may have been written over.}
  644.  
  645.  
  646. PROCEDURE Comdeinstall;
  647. BEGIN
  648.   IF ComInstalled THEN
  649.     BEGIN
  650.  
  651.     ComInstalled := False;
  652.  
  653.     {Restore Modem-Control-Register and Interrupt-Enable-Register.}
  654.  
  655.     Port [uart_mcr] := old_mcr;
  656.     Port [uart_ier] := old_ier;
  657.  
  658.     {Restore appropriate bit of interrupt controller's mask}
  659.  
  660.     disable_interrupts;
  661.     Port [$21] := Port [$21] AND NOT i8259bit OR
  662.      old_i8259_mask AND i8259bit;
  663.     enable_interrupts;
  664.  
  665.     {Reset the interrupt vector}
  666.  
  667.     SetIntVec (intnum, old_vector);
  668.  
  669.     END;
  670. END;
  671.  
  672.  
  673. {This procedure is called when the program exits for any reason.  It
  674. deinstalls the interrupt driver.}
  675.  
  676. {$F+} PROCEDURE exit_procedure; {$F-}
  677. BEGIN
  678.   Comdeinstall;
  679.   ExitProc := exit_save;
  680. END;
  681.  
  682.  
  683. {This installs the exit procedure.}
  684.  
  685. BEGIN
  686.   exit_save := ExitProc;
  687.   ExitProc := @exit_procedure;
  688. END.
  689.